using System;
using System.Text;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace PHSRAG.WebControls
{
	/// <summary>
	/// Specialized <see cref="DataGrid"/> featuring fixed header and footer with scrollable contents.
	/// </summary>
	public class ScrollableDataGrid : DataGrid	
	{
		private Table _pagerTable;
		private Table _headerTable;
		private Table _footerTable;

		private static readonly Unit DefaultHeaderHeight = 35;
		private static readonly Unit DefaultFooterHeight = 25;
		private static readonly Unit DefaultPagerHeight = 25;

		public override Unit Height
		{
			get { return base.Height; }
			set
			{
				if (value.Type != UnitType.Pixel)
					throw new ArgumentException("Height must be specified in pixels.");

				base.Height = value;
			}
		}

		protected override void CreateControlHierarchy(bool useDataSource)
		{
			try
			{
				base.CreateControlHierarchy(useDataSource);

				// Create the seperate header table.
				if (this.ShowHeader)
				{
					_headerTable = new Table();
					_headerTable.ID = "header";
					this.Controls.Add(_headerTable);
				}

				if (this.ShowFooter)
				{
					_footerTable = new Table();
					_footerTable.ID = "footer";
					this.Controls.Add(_footerTable);
				}

				if (this.AllowPaging)
				{
					_pagerTable = new Table();
					_pagerTable.ID = "pager";
					this.Controls.Add(_pagerTable);
				}
			}
			catch (Exception)
			{
				throw;
			}
		}

		private static void AddEmptyRow(Table table, int columnCount)
		{
			DataGridItem emptyRow = new DataGridItem(table.Rows.Count, -1, ListItemType.Item);
			emptyRow.Height = Unit.Percentage(100);
			emptyRow.Attributes["height"] = "100%";
			emptyRow.BackColor = Color.Transparent;

			for (int i=0; i<columnCount; i++)
			{
				TableCell cell = new TableCell();
				cell.Text = "&nbsp;";
				emptyRow.Cells.Add(cell);
			}

			table.Rows.Add(emptyRow);
		}

		/// <summary>
		/// Ensure that both the item, header, and footer all have the same widths.
		/// </summary>
		/// <param name="lastVisibleIndex"></param>
		private void PrepareColumnWidths()
		{
			Unit width;
			for (int i=0; i<this.Columns.Count; i++)
			{
				width = Unit.Empty;
				width = this.Columns[i].ItemStyle.Width;
				if (width == Unit.Empty)
				{
					width = this.Columns[i].HeaderStyle.Width;
					if (width == Unit.Empty)
						width = this.Columns[i].FooterStyle.Width;
				}
				if (width != Unit.Empty)
				{
					this.Columns[i].ItemStyle.Width = width;
					this.Columns[i].HeaderStyle.Width = width;
					this.Columns[i].FooterStyle.Width = width;
				}
			}
		}

		/// <summary>
		/// Finds the number of visible columns and the index of the last visible column.
		/// </summary>
		/// <returns></returns>
		private void FindVisibleColumns(out int visibleColumns, out int lastVisibleIndex)
		{
			lastVisibleIndex = -1;
			visibleColumns = 0;
			
			for (int i = this.Columns.Count -1; i>=0; i--)
			{
				if (this.Columns[i].Visible)
				{
					if (lastVisibleIndex == -1)
						lastVisibleIndex = i;

					visibleColumns++;
				}					
			}
			if (visibleColumns == 0)
        throw new InvalidOperationException("The grid does not contain a single visible column.");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnItemDataBound(DataGridItemEventArgs e)
		{
			base.OnItemDataBound (e);
			DataGridItem item = e.Item;
		
			if(item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem )
			{
				item.Style["font-size"] = "11px";
				item.Height = this.FooterStyle.Height;
				if (item.ItemType == ListItemType.AlternatingItem)
					item.Style["background-color"] = "#ffffff";
			}
			else if ( item.ItemType == ListItemType.Header)
			{
				item.Cells[item.Cells.Count - 1].Style["text-align"] = "center";
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected override void PrepareControlHierarchy()
		{
			try
			{
				int lastVisibleIndex;
				int visibleColumns;
				FindVisibleColumns(out visibleColumns, out lastVisibleIndex);

				PrepareColumnWidths();

				//TODO: I don't like this temp TableStyle approach.
				TableStyle tempGridStyle = new TableStyle();
				tempGridStyle.CopyFrom(this.ControlStyle);
				this.ControlStyle.Reset();
				
				this.ControlStyle.Height = Unit.Percentage(100);
				((TableStyle)this.ControlStyle).CellSpacing = tempGridStyle.CellSpacing;
				((TableStyle)this.ControlStyle).CellPadding = tempGridStyle.CellPadding;
				
				base.PrepareControlHierarchy();		
	      
				this.ControlStyle.CopyFrom(tempGridStyle);

				Table dataGridTable = this.Controls[0] as Table;
				dataGridTable.GridLines = GridLines.None;
				dataGridTable.Width = Unit.Percentage(100);
				dataGridTable.Style["table-layout"] = "fixed";

				if (this.AllowPaging)
					PreparePagerTable(dataGridTable);

				if (this.ShowHeader)
					PrepareHeaderTable(dataGridTable, lastVisibleIndex);

				// Move the footer row from the DataGridTable to the FooterTable.
				if (this.ShowFooter)
					PrepareFooterTable(dataGridTable, lastVisibleIndex);

				// Add an additional row to the table to occupy any remaining vertical space in the grid.
				AddEmptyRow(dataGridTable, visibleColumns);

				if (this.GridLines != GridLines.None)
					PrepareGridLines(dataGridTable, lastVisibleIndex);
			}
			catch( Exception )
			{
				throw;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="table"></param>
		/// <param name="lastVisibleIndex"></param>
		private void PrepareGridLines(Table table, int lastVisibleIndex)
		{			
			BordersInfo borders = new BordersInfo(this.ItemStyle);
			for (int i=0; i<table.Rows.Count; i++)
			{
				DataGridItem item = (DataGridItem)table.Rows[i];
				switch (item.ItemType)
				{
					case ListItemType.Item : 
					case ListItemType.AlternatingItem :		
						// Clear out the border information on the row.
						ClearRowBorder(item);

						for (int j=0; j<item.Cells.Count; j++)
						{
							borders.ShowTop = false;
							borders.ShowLeft = false;

							// Only show the bottom border if this is not the last row.
							borders.ShowBottom = ((this.GridLines == GridLines.Horizontal || this.GridLines == GridLines.Both) && i != table.Rows.Count - 1);
              
							// Only show the right border if vertical gridlines are to be shown and 
							// this is not the right-most visible column
							borders.ShowRight = ((this.GridLines == GridLines.Vertical || this.GridLines == GridLines.Both) && j != lastVisibleIndex);

							PrepareCellBorders(item.Cells[j], borders);							
						}
					break;
				}
			}
		}

		/// <summary> 
		/// Render this control to the output parameter specified.
		/// </summary>
		/// <param name="output"> The HTML writer to write out to </param>
		protected override void Render(HtmlTextWriter writer)
		{
			try
			{
				this.PrepareControlHierarchy();

				// Instantiate a new HtmlTextWriter instead of writing directly to the writer passed as 
				// a parameter to the Render method.  The method parameter may be an Html32TextWriter which uses 
				// adaptive rendering.  See http://www.aspnetresources.com/blog/adaptive_rendering_redux.aspx for details.
				HtmlTextWriter output = new HtmlTextWriter(writer);

				output.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
				this.ControlStyle.AddAttributesToRender(output);

				// Without this attribute IE cuts off the border on the right edge.
				output.AddStyleAttribute("overflow", "hidden");
				output.AddStyleAttribute("position", "relative");
				output.AddStyleAttribute("padding", "0px");

				output.RenderBeginTag(HtmlTextWriterTag.Div);  // Open the wrapper div

				if (_headerTable != null)
					_headerTable.RenderControl(output);

				output.AddStyleAttribute("position", "absolute");

				Double gridHeight = this.Height.Value;
				if (this.ShowHeader)
					gridHeight -= this.HeaderStyle.Height.Value;
				if (this.ShowFooter)
					gridHeight -= this.FooterStyle.Height.Value;
				if (this.AllowPaging)
					gridHeight -= this.PagerStyle.Height.Value;

				output.AddStyleAttribute(HtmlTextWriterStyle.Height, gridHeight + "px");
				output.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%");
				output.AddStyleAttribute("overflow", "auto");

				if (this.ShowHeader)
					output.AddStyleAttribute("top", this.HeaderStyle.Height.ToString());
				
				int distanceFromBottom = 0;
				if (this.AllowPaging)
					distanceFromBottom += Convert.ToInt32(this.PagerStyle.Height.Value);
				if (this.ShowFooter)
					distanceFromBottom += Convert.ToInt32(this.FooterStyle.Height.Value);

				output.AddStyleAttribute("bottom", distanceFromBottom.ToString() + "px");
				output.RenderBeginTag(HtmlTextWriterTag.Div);
	      
				// Render the DataGridTable.
				this.Controls[0].RenderControl(output);

				output.RenderEndTag(); // div

				if (_footerTable != null)
					_footerTable.RenderControl(output);

				if (_pagerTable != null)
					_pagerTable.RenderControl(output);
					
				output.RenderEndTag(); // Close the outer wrapper div
			}
			catch (Exception)
			{
				throw;
			}
		}

		private void PreparePagerTable(Table dataGridTable)
		{
			if (this.PagerStyle.Height == Unit.Empty)
				this.PagerStyle.Height = DefaultPagerHeight;

			// Remove the top pager.
			if (dataGridTable.Rows.Count == 0)
				return;

			dataGridTable.Rows.RemoveAt(0);

			TableRow pagerRow = dataGridTable.Rows[dataGridTable.Rows.Count - 1];
			dataGridTable.Rows.Remove(pagerRow);
			_pagerTable.Rows.Add(pagerRow);

			_pagerTable.Width = Unit.Percentage(100);
			_pagerTable.Style["position"] = "absolute";
			_pagerTable.Style["bottom"] = "0px";
			_pagerTable.Style["left"] = "0px";
		}

		private void PrepareHeaderTable(Table dataGridTable, int lastVisibleColumn)
		{
			if (this.HeaderStyle.Height == Unit.Empty)
				this.HeaderStyle.Height = DefaultHeaderHeight;

			if (dataGridTable.Rows.Count == 0)
				return;

			// Move the header row from the DataGridTable to the HeaderTable.
			TableRow headerRow = dataGridTable.Rows[0];
			dataGridTable.Rows.Remove(headerRow);
			_headerTable.Rows.Add(headerRow);

			_headerTable.GridLines = GridLines.None;
			_headerTable.Style["position"] = "absolute";
			_headerTable.Style["table-layout"] = "fixed";
			_headerTable.Width = Unit.Percentage(100);
			_headerTable.Height = this.HeaderStyle.Height;

			_headerTable.Style["top"] = "0px";
			_headerTable.Style["left"] = "0px";
			_headerTable.Style["text-align"] = "right";

			_headerTable.Style["background-color"] = "#c0c0c0";
			_headerTable.Style["color"] = "#08246b";
			_headerTable.Style["font-weight"] = "bold";

			ClearRowBorder(headerRow);

			BordersInfo borders = new BordersInfo(this.HeaderStyle);
			for (int i=0; i<this.Columns.Count; i++)
			{
				borders.ShowTop = false;
				borders.ShowLeft = false;
				borders.ShowBottom = (this.GridLines == GridLines.Horizontal || this.GridLines == GridLines.Both);
				borders.ShowRight = ((this.GridLines == GridLines.Vertical || this.GridLines == GridLines.Both) && i != lastVisibleColumn);
				PrepareCellBorders(headerRow.Cells[i], borders);				
			}

      if (this.CellSpacing != -1)
				_headerTable.CellSpacing = this.CellSpacing;
			if (this.CellPadding != -1)
				_headerTable.CellPadding = this.CellPadding;
		}

		private void PrepareFooterTable(Table dataGridTable, int lastVisibleColumn)
		{
			// Ensure that a FooterHeight has been specified.
			if (this.FooterStyle.Height == Unit.Empty)
				this.FooterStyle.Height = DefaultFooterHeight;

			if (dataGridTable.Rows.Count == 0)
				return;

			// Move the footer row from the DataGridTable to the FooterTable.
			TableRow footerRow = dataGridTable.Rows[dataGridTable.Rows.Count - 1];
			dataGridTable.Rows.Remove(footerRow);
			_footerTable.Rows.Add(footerRow);

			_footerTable.Style["position"] = "absolute";
			_footerTable.Style["table-layout"] = "fixed";
			_footerTable.Style["bottom"] = this.AllowPaging ? this.PagerStyle.Height.ToString() : "0px";
			_footerTable.Width = Unit.Percentage(100);
			_footerTable.Height = this.HeaderStyle.Height;
			_footerTable.Style["text-align"] = "right";

			_footerTable.Style["left"] = "0px";
			_footerTable.Style["text-align"] = "right";

			_footerTable.Style["background-color"] = "#c0c0c0";
			_footerTable.Style["color"] = "black";
			_footerTable.Style["font-weight"] = "bold";

			
			ClearRowBorder(footerRow);

			BordersInfo borders = new BordersInfo(this.FooterStyle);
			for (int i=0; i<this.Columns.Count; i++)
			{
				borders.ShowBottom = false;
				borders.ShowLeft = false;
				borders.ShowTop = (this.GridLines == GridLines.Horizontal || this.GridLines == GridLines.Both);
				borders.ShowRight = ((this.GridLines == GridLines.Vertical || this.GridLines == GridLines.Both) && i != lastVisibleColumn);
				PrepareCellBorders(footerRow.Cells[i], borders);				
			}

			if (this.CellSpacing != -1)
				_footerTable.CellSpacing = this.CellSpacing;
			if (this.CellPadding != -1)
				_footerTable.CellPadding = this.CellPadding;
		}

		private static void PrepareCellBorders(TableCell cell, BordersInfo borders)
		{
			if (borders.Color != Color.Empty)
				cell.Style["border-color"] = borders.Color.Name;
			if (borders.Width != Unit.Empty)
				cell.Style["border-width"] = borders.Width.ToString();
			
			// Only show the bottom border if this is not the last row.
			if (borders.ShowTop)
				cell.Style["border-top-style"] = borders.Style.ToString();

			if (borders.ShowLeft)
				cell.Style["border-left-style"] = borders.Style.ToString();

			if (borders.ShowBottom)
				cell.Style["border-bottom-style"] = borders.Style.ToString();

			if (borders.ShowRight)
				cell.Style["border-right-style"] = borders.Style.ToString();
		}

		private static void ClearRowBorder(TableRow row)
		{
			row.BorderColor = Color.Empty;
			row.BorderStyle = BorderStyle.NotSet;
			row.BorderWidth = Unit.Empty;
		}
	}

	public struct BordersInfo
	{
		private bool showTop;
		private bool showRight;
		private bool showLeft;
		private bool showBottom;
		private Unit width;
		private Color color;
		private BorderStyle style;

		public BordersInfo(TableItemStyle style)
		{
			this.width = style.BorderWidth;
			this.style = style.BorderStyle;
			this.color = style.BorderColor;

			this.showTop = false;
			this.showRight = false;
			this.showBottom = false;
			this.showLeft = false;
		}

		public bool ShowTop { get { return this.showTop; } set { this.showTop = value; }}
		public bool ShowLeft { get { return this.showLeft; } set { this.showLeft = value; }}
		public bool ShowRight { get { return this.showRight; } set { this.showRight = value; }}
		public bool ShowBottom { get { return this.showBottom; } set { this.showBottom = value; }}
		public Color Color { get { return this.color; } set { this.color = value; }}
		public Unit Width { get { return this.width; } set { this.width = value; }}
		public BorderStyle Style { get { return this.style; } set { this.style = value; }}
	}
}